const Spec = [
  // Whitespace:
  [/^\s+/, null],

  // Comments:
  // Skip single-line comments:
  [/^\/\/.*/, null],
  // Skip multi-line comments:
  [/^\/\/*[\s\S]*?\*\//, null],

  // Symbols, delimiters: 分号
  [/^;/, ";"],

  // Numbers:
  [/^\d+/, "NUMBER"],

  // Strings:
  [/^"[^"]*"/, "STRING"],
  [/^'[^']*'/, "STRING"],
];

class Tokenizer {
  init(string) {
    this._string = string;
    this._cursor = 0;
  }

  isEOF() {
    return this._cursor === this._string.length;
  }
  hasMoreTokens() {
    return this._cursor < this._string.length;
  }

  getNextToken() {
    if (!this.hasMoreTokens()) {
      return null;
    }

    const string = this._string.slice(this._cursor);

    //#region 第一写法
    /* 第一写法
    // Numbers:
    if (!Number.isNaN(Number(string[0]))) {
      let number = "";
      while (!Number.isNaN(Number(string[this._cursor]))) {
        number += string[this._cursor++];
      }

      return {
        type: "NUMBER",
        value: number,
      };
    }

    // String:
    if (string[0] === '"') {
      let s = "";
      do {
        s += string[this._cursor++];
      } while (string[this._cursor] !== '"' && !this.isEOF());

      s += string[this._cursor++]; // skip "

      return {
        type: "STRING",
        value: s,
      };
    }
    */
    //#endregion

    //#region 第二写法
    /* 第二写法
    // Numbers: /^\d+/
    let matched = /^\d+/.exec(string);
    if (matched !== null) {
      this._cursor += matched[0].length;
      return {
        type: "NUMBER",
        value: matched[0],
      };
    }

    // String: /^"[^"]*"/ 双引号
    matched = /^"[^"]*"/.exec(string);
    if (matched !== null) {
      this._cursor += matched[0].length;

      return {
        type: "STRING",
        value: matched[0],
      };
    }

    // String: /^'[^']*'/ 单引号
    matched = /^'[^']*'/.exec(string);
    if (matched !== null) {
      this._cursor += matched[0].length;

      return {
        type: "STRING",
        value: matched[0],
      };
    }
    */
    //#endregion

    //#region 第三写法
    for (const [regexp, tokenType] of Spec) {
      const tokenValue = this._match(regexp, string);
      if (tokenValue == null) {
        continue;
      }

      // Should skip token, e.g. whitespace
      if (tokenType == null) {
        return this.getNextToken();
      }
      return {
        type: tokenType,
        value: tokenValue,
      };
    }
    //#endregion

    throw new SyntaxError(`Unexpected token: "${string[0]}"`);
  }

  _match(regexp, string) {
    const matched = regexp.exec(string);
    if (matched !== null) {
      this._cursor += matched[0].length;
      return matched[0];
    }
  }
}

module.exports = {
  Tokenizer,
};
